// @file rwlock.h generic reader-writer lock (cross platform support)

/*
 *    Copyright (C) 2010 10gen Inc.
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *    As a special exception, the copyright holders give permission to link the
 *    code of portions of this program with the OpenSSL library under certain
 *    conditions as described in each individual source file and distribute
 *    linked combinations including the program with the OpenSSL library. You
 *    must comply with the GNU Affero General Public License in all respects
 *    for all of the code used other than as permitted herein. If you modify
 *    file(s) with this exception, you may extend this exception to your
 *    version of the file(s), but you are not obligated to do so. If you do not
 *    wish to do so, delete this exception statement from your version. If you
 *    delete this exception statement from all source files in the program,
 *    then also delete it in the license file.
 */

#pragma once

#include "mongo/util/concurrency/mutex.h"
#include "mongo/util/concurrency/rwlockimpl.h"
#include "mongo/util/concurrency/simplerwlock.h"
#include "mongo/util/concurrency/threadlocal.h"
#include "mongo/util/debug_util.h"
#include "mongo/util/time_support.h"

namespace mongo {

class RWLock : public RWLockBase {
    enum {
        NilState,
        UpgradableState,
        Exclusive
    } x;  // only bother to set when doing upgradable related things
public:
    const char* const _name;
    RWLock(const char* name) : _name(name) {
        x = NilState;
    }
    void lock() {
        RWLockBase::lock();
    }
    void unlock() {
        RWLockBase::unlock();
    }

    void lock_shared() {
        RWLockBase::lock_shared();
    }
    void unlock_shared() {
        RWLockBase::unlock_shared();
    }

private:
    void lockAsUpgradable() {
        RWLockBase::lockAsUpgradable();
    }
    void unlockFromUpgradable() {  // upgradable -> unlocked
        RWLockBase::unlockFromUpgradable();
    }

public:
    void upgrade() {  // upgradable -> exclusive lock
        verify(x == UpgradableState);
        RWLockBase::upgrade();
        x = Exclusive;
    }

    bool lock_shared_try(int millis) {
        return RWLockBase::lock_shared_try(millis);
    }

    bool lock_try(int millis = 0) {
        return RWLockBase::lock_try(millis);
    }

    /** acquire upgradable state.  You must be unlocked before creating.
        unlocks on destruction, whether in upgradable state or upgraded to exclusive
        in the interim.
        */
    class Upgradable {
        MONGO_DISALLOW_COPYING(Upgradable);
        RWLock& _r;

    public:
        Upgradable(RWLock& r) : _r(r) {
            r.lockAsUpgradable();
            verify(_r.x == NilState);
            _r.x = RWLock::UpgradableState;
        }
        ~Upgradable() {
            if (_r.x == RWLock::UpgradableState) {
                _r.x = NilState;
                _r.unlockFromUpgradable();
            } else {
                // TEMP                     verify( _r.x == Exclusive ); // has been upgraded
                _r.x = NilState;
                _r.unlock();
            }
        }
    };
};

/** throws on failure to acquire in the specified time period. */
class rwlock_try_write {
    MONGO_DISALLOW_COPYING(rwlock_try_write);

public:
    struct exception {};
    rwlock_try_write(RWLock& l, int millis = 0) : _l(l) {
        if (!l.lock_try(millis))
            throw exception();
    }
    ~rwlock_try_write() {
        _l.unlock();
    }

private:
    RWLock& _l;
};

class rwlock_shared {
    MONGO_DISALLOW_COPYING(rwlock_shared);

public:
    rwlock_shared(RWLock& rwlock) : _r(rwlock) {
        _r.lock_shared();
    }
    ~rwlock_shared() {
        _r.unlock_shared();
    }

private:
    RWLock& _r;
};

/* scoped lock for RWLock */
class rwlock {
    MONGO_DISALLOW_COPYING(rwlock);

public:
    /**
     * @param write acquire write lock if true sharable if false
     * @param lowPriority if > 0, will try to get the lock non-greedily for that many ms
     */
    rwlock(const RWLock& lock,
           bool write,
           /* bool alreadyHaveLock = false , */ int lowPriorityWaitMS = 0)
        : _lock((RWLock&)lock), _write(write) {
        {
            if (_write) {
                _lock.lock();
            } else {
                _lock.lock_shared();
            }
        }
    }
    ~rwlock() {
        if (_write)
            _lock.unlock();
        else
            _lock.unlock_shared();
    }

private:
    RWLock& _lock;
    const bool _write;
};

// ----------------------------------------------------------------------------------------

/** recursive on shared locks is ok for this implementation */
class RWLockRecursive : protected RWLockBase {
protected:
    ThreadLocalValue<int> _state;
    void
    lock();  // not implemented - Lock() should be used; didn't overload this name to avoid mistakes
    virtual void Lock() {
        RWLockBase::lock();
    }

public:
    virtual ~RWLockRecursive() {}
    const char* const _name;
    RWLockRecursive(const char* name) : _name(name) {}

    void assertAtLeastReadLocked() {
        verify(_state.get() != 0);
    }
    void assertExclusivelyLocked() {
        verify(_state.get() < 0);
    }

    class Exclusive {
        MONGO_DISALLOW_COPYING(Exclusive);
        RWLockRecursive& _r;

    public:
        Exclusive(RWLockRecursive& r) : _r(r) {
            int s = _r._state.get();
            dassert(s <= 0);
            if (s == 0)
                _r.Lock();
            _r._state.set(s - 1);
        }
        ~Exclusive() {
            int s = _r._state.get();
            DEV wassert(s < 0);  // wassert: don't throw from destructors
            ++s;
            _r._state.set(s);
            if (s == 0)
                _r.unlock();
        }
    };

    class Shared {
        MONGO_DISALLOW_COPYING(Shared);
        RWLockRecursive& _r;
        bool _alreadyLockedExclusiveByUs;

    public:
        Shared(RWLockRecursive& r) : _r(r) {
            int s = _r._state.get();
            _alreadyLockedExclusiveByUs = s < 0;
            if (!_alreadyLockedExclusiveByUs) {
                dassert(s >= 0);  // -1 would mean exclusive
                if (s == 0)
                    _r.lock_shared();
                _r._state.set(s + 1);
            }
        }
        ~Shared() {
            if (_alreadyLockedExclusiveByUs) {
                DEV wassert(_r._state.get() < 0);
            } else {
                int s = _r._state.get() - 1;
                DEV wassert(s >= 0);
                _r._state.set(s);
                if (s == 0)
                    _r.unlock_shared();
            }
        }
    };
};

class RWLockRecursiveNongreedy : public RWLockRecursive {
    virtual void Lock() {
        bool got = false;
        for (int i = 0; i < lowPriorityWaitMS; i++) {
            if (lock_try(0)) {
                got = true;
                break;
            }
            int sleep = 1;
            if (i > (lowPriorityWaitMS / 20))
                sleep = 10;
            sleepmillis(sleep);
            i += (sleep - 1);
        }
        if (!got) {
            RWLockBase::lock();
        }
    }

public:
    const int lowPriorityWaitMS;
    RWLockRecursiveNongreedy(const char* nm, int lpwaitms)
        : RWLockRecursive(nm), lowPriorityWaitMS(lpwaitms) {}
    const char* implType() const {
        return RWLockRecursive::implType();
    }

    // just for testing:
    bool __lock_try(int millis) {
        return RWLockRecursive::lock_try(millis);
    }
};
}
